#include <stdio.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>

#define LEFT "["
#define MAX_COMMAND_NUM 32
#define COMMAND_LINE 32
#define PROMPT "#"
#define RIGHT "]"
#define LINE_SIZE 1024
#define WEXIT_CODE 44
#define ENV_SIZE 32
#define NONE 0
#define RD_OTRUNC 1
#define RD_OAPPAND 3
#define RD_IW 2

int last_code = 0, quit = 0;
char pwd[LINE_SIZE];
char command[COMMAND_LINE];
char *splict_command[COMMAND_LINE];
char new_env[ENV_SIZE][LINE_SIZE];
int new_env_num = 0;
char *argv[COMMAND_LINE];
int argc = 0;
int rdio = NONE;
char *rdioFilename = NULL;

char *getUser()
{
	return getenv("USER");
}

char *getHostname()
{
	return getenv("HOSTNAME");
}

void getPwd()
{
	getcwd(pwd, sizeof pwd);
}

// 交互命令行绘制
void IntracteLine()
{
	getPwd();
	size_t pos = strlen(pwd) - 1;
	while (pos > 0 && pwd[pos] != '/')
		--pos;
	if (pos != strlen(pwd) - 1)
		++pos;
	printf(LEFT "%s@%s %s" RIGHT PROMPT, getUser(), getHostname(), pwd + pos);
	char *s = fgets(command, sizeof command, stdin);
	assert(s);
	(void)s;
	command[strlen(command) - 1] = '\0';
	// printf("echo: %s\n", command);
}

// 清除字符串前后空格
void strtrim(char **str)
{
	size_t len = strlen(*str);
	if (**str == '\0')
		return;
	while (isspace(**str))
		(*str)++;
	char *strEnd = *str + len - 1;
	while (strEnd >= (*str) && isspace(*strEnd))
		strEnd--;
	*(strEnd + 1) = '\0';
}

// 对命令进行分行
int splictCommand()
{
	int i = 0;
	splict_command[i++] = strtok(command, "|");
	if (splict_command[i - 1])
		strtrim(&splict_command[i - 1]);
	while (splict_command[i++] = strtok(NULL, "|"))
		strtrim(&splict_command[i - 1]);
	return i - 1;
}

// 解析单命令字符串
int parseCommand(size_t pos, char *_argv[])
{
	for (size_t i = 0; i < strlen(splict_command[pos]); ++i)
	{
		// 输入重定向
		if (splict_command[pos][i] == '<')
		{
			rdio = RD_IW;
			splict_command[pos][i] = '\0';
			++i;
			while (isspace(splict_command[pos][i]))
				++i;
			rdioFilename = splict_command[pos] + i;
			break;
		}
		// 输出重定向
		else if (splict_command[pos][i] == '>')
		{
			// 如果是追加重定向
			if (splict_command[pos][i + 1] == '>')
			{
				splict_command[pos][i++] = '\0';
				splict_command[pos][i++] = '\0';
				rdio = RD_OAPPAND;
			}
			else
			{
				splict_command[pos][i++] = '\0';
				rdio = RD_OTRUNC;
			}
			while (isspace(splict_command[pos][i]))
				++i;
			rdioFilename = splict_command[pos] + i;
			break;
		}
	}
	int i = 0;
	argv[i++] = strtok(splict_command[pos], " \t");
	while (_argv[i++] = strtok(NULL, " \t"))
		;
	return i - 1;
}

// 运行内建命令
int buildCommand(int _argc, char *_argv[])
{
	// 执行内建命令
	// 1. cd命令一定是内建命令，这是由于修改子进程的工作目录对父进程没有任何影响
	if (_argc == 2 && strcmp(_argv[0], "cd") == 0)
	{
		chdir((const char *)_argv[1]);
		getPwd();
		strcpy(getenv("PWD"), pwd);
		return 1;
	}
	// 2. export
	// 这里需要注意的点是在putenv的时候由于没有进行拷贝操作，所以需要维护一块空间专门用来存放新增的环境变量
	// 从而达到避免环境变量覆盖的问题
	else if (_argc == 2 && strcmp(_argv[0], "export") == 0)
	{
		strcpy(new_env[new_env_num++], _argv[1]);
		putenv(new_env[new_env_num - 1]);
		return 1;
	}
	else if (strcmp(_argv[0], "quit") == 0)
	{
		quit = 1;
		return 1;
	}
	// 3. echo
	else if (argc == 2 && strcmp(_argv[0], "echo") == 0)
	{
		if (*_argv[1] == '$')
		{
			if (strcmp(_argv[1] + 1, "?") == 0)
				printf("%d\n", last_code);
			else
			{
				char *env = getenv(_argv[1] + 1);
				printf("%s\n", env);
			}
		}
		else
		{
			if (*_argv[1] == '"')
			{
				_argv[1][strlen(_argv[1]) - 1] = '\0';
				printf("%s\n", _argv[1] + 1);
			}
			else
				printf("%s\n", argv[1]);
		}
		return 1;
	}
	if (strcmp(_argv[0], "ls") == 0)
		// 添加颜色选项
		_argv[argc++] = "--color";
	return 0;
}

// 运行普通命令
int normalCommand(int rfd, int i, int num, char *argv[])
{
	//只要任务数量不是1，就说明需要用到管道
	int pipefd[2];
	if(num != 1 && i != num - 1)
		pipe(pipefd);
	pid_t id = fork();

	if (id < 0)
		perror("fork()");
	if (id == 0)
	{
		// 对子进程进行重定向
		if (rdio == RD_IW)
		{
			int fd = open(rdioFilename, O_RDONLY);
			if (fd < 0)
				perror("open fail!\n");
			dup2(fd, 0);
		}
		else if (rdio == RD_OTRUNC)
		{
			int fd = open(rdioFilename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
			if (fd < 0)
				perror("open fail!\n");
			dup2(fd, 1);
		}
		else if (rdio == RD_OAPPAND)
		{
			int fd = open(rdioFilename, O_WRONLY | O_CREAT | O_APPEND, 0666);
			if (fd < 0)
				perror("open faile!\n");
			dup2(fd, 1);
		}
		if(num != 1 && i != num - 1)
			close(pipefd[0]);
		// 对子进程进行管道重定向
		if(num!= 1 && i == 0)
			dup2(pipefd[1], 1);
		else if(i == num - 1)
		{
			dup2(rfd, 0);
		}
		else
		{
			dup2(pipefd[1], 1);
			dup2(rfd, 0);
		}
		

		// 执行子进程的逻辑
		execvp(argv[0], argv);

		// 如果程序到这里了，说明替换失败，直接异常退出
		exit(WEXIT_CODE);
	}
	if(num != 1 && i != num - 1)
		close(pipefd[1]);
	int status = 0;
	pid_t ret = waitpid(-1, &status, 0);
	return pipefd[0];
	if (ret < 0)
		perror("waitpid()");
	last_code = WEXITSTATUS(status);
}



int main()
{
	while (!quit)
	{
		// 1. 在进行下一条指令之前要清楚有可能存在的重定向
		rdio = NONE;
		rdioFilename = NULL;
		// 2. 绘制命令行窗口
		IntracteLine();

		// 分出多条命令
		int num = splictCommand();
		// debug
		//  printf("num: %d\n", num);
		//  for(int i = 0; i < num; ++i)
		//  	printf("%s %d\n", splict_command[i], strlen(splict_command[i]));

		// 3.解析命令符(strtok())
		// 进行单条命令执行
		int nextRfd = 0;
		for (int i = 0; i < num; ++i)
		{
			argc = parseCommand(i, argv);

			// for(int i = 0; i < argc; ++i)
			//     printf("%s\n", argv[i]);
			// printf("rdioFilename:%s, rdio:%d",rdioFilename, rdio );
			// debug
			// for(int i = 0; i < argc; ++i)
			//     printf("%s\n", argv[i]);
			

			// 4.判断内建命令
			int ret = buildCommand(argc, argv);
			// 5.执行普通命令
			if (!ret)
				nextRfd = normalCommand(nextRfd, i, num, argv);
		}
	}
	return 0;
}
